def index():

    import uuid

    if not session.trees:
        session.trees = { }
        session.treeIndex = 0
        session.myId = uuid.uuid1()
        session.config = { 'cladeColor': 'black',
                           'buttonColor': 'blue',
                           'buttonRadius': 5,
                           'labelColor': 'black',
                           'fontFamily': 'courier new, times, arial, verdana, sans-serif',
                           'fontSize': 14,
                           'labelBuffer': 0,
                           'nodeSeparation': 20,
                           'generationSeparation': 20,
                           'canvasBuffer': 100,
                           'pathWidth': 3,
                           'maxTips': 200 }

    for (k,v) in session.trees.items():
        myTree = getTree( { 'treeId': k } )
        if myTree == None:
            del session.trees[k]

    treeInfo = { 'source': session.trees, 'maxTips': session.config['maxTips'] }

    return dict( treeInfo = treeInfo )



def saveConfig():

    for (k, v) in  session.config.items():
        if k in request.vars:
            session.config[k] = request.vars[k]



def addNewickToSession( p ):

    newick = local_import('newick')

    tree = None

    try:
        tree = newick.parse( p['newickString'] )
    except:
        return 'parseError'
    
    treeId = ''.join( [ str( session.myId ), str( session.treeIndex ) ] )

    storeTree( { 'treeId': treeId, 'tree': tree } )

    session.trees[treeId] = { 'name': request.vars.newickName,
                              'comment': request.vars.newickComment,
                              'visibleList': [],
                              'collapseDict': {},
                              'scaling': 'smooth',
                              'supportValues': { 'threshold': '', 'thickness': '' } }

    session.treeIndex = session.treeIndex + 1

    return treeId


def newickUpload():

    return addNewickToSession( { 'newickString': request.vars.newickFile.file.read(),
                                 'name': request.vars.newickName,
                                 'comment': request.vars.newickComment } )

def addNewick():

    if request.vars.newickName == '': request.vars.newickName = ''.join( [ 'Untitled', str(session.treeIndex) ] )

    return addNewickToSession( { 'newickString': request.vars.newickString,
                                 'name': request.vars.newickName,
                                 'comment': request.vars.newickComment } )
                                        

def getTree( p ):
    return cache.ram( p['treeId'], lambda:None, time_expire = None )


def clearTreeCache( p ):
    cache.ram.clear( regex = p['treeId'] )

def storeTree( p ) :
    cache.ram( p['treeId'], lambda:p['tree'], None )


def viewer():

    newick = local_import('newick')
    collapse = local_import('auto_collapse')
 
    treeId = request.args[0]
    maxTips = int(request.args[1])
 
    tree = getTree( { 'treeId': treeId } )
 
    collapsed = session.trees[ treeId ]['collapseDict']
    visibleList = session.trees[ treeId ]['visibleList']
 
    collapse.auto_collapse( tree, collapsed, visibleList, maxTips )
 
    removeCollapsed( { 'node' : tree, 'collapsed' : collapsed } )
     
    clearTreeCache( { 'treeId': treeId } )
    
    storeTree( { 'treeId': treeId, 'tree': tree } )

    return { 'treeId': treeId,
             'config': session.config,
             'scaling': session.trees[treeId]['scaling'],
             'supportValues': session.trees[treeId]['supportValues'] }
 
 

def retrieveTree():

    return getTree( { 'treeId': request.vars.treeId } ).emit_json()



def addSupportValueAnnotation():

    session.trees[ request.vars.treeId ]['supportValues'] = { 'threshold': request.vars.threshold,
                                                              'thickness': request.vars.thickness }

    return ','.join( [ request.vars.threshold, request.vars.thickness ] )

def removeSupportValueAnnotation():
    session.trees[ request.vars.treeId ]['supportValues'] = { 'threshold': None,
                                                              'thickness': None }


def rotate():
    
    cladeId = int(request.vars.cladeId)
    
    tree = getTree( { 'treeId': request.vars.treeId } )
    
    cladeToRotate = tree.findNodeById(cladeId)

    if request.args[0] == 'forward':
        cladeToRotate.children.insert( 0, cladeToRotate.children.pop() )
    else:
        cladeToRotate.children.append( cladeToRotate.children.pop(0) )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()



def ladderize():

    newick = local_import('newick')

    cladeId = int(request.vars.cladeId)

    tree = getTree( { 'treeId': request.vars.treeId } )

    cladeToLadderize = tree.findNodeById(cladeId)

    cladeToLadderize.order_subtrees_by_size( None, True, cladeToLadderize.isLadderized )

    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()


def mrca():
        
    newick = local_import('newick')

    nodeId = int(request.vars.nodeId)

    tree = getTree( { 'treeId': request.vars.treeId } )

    newRoot = tree.findNodeById(nodeId)
    newRoot.isRoot = True

    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': newRoot } )
    
    return newRoot.emit_json()



def pruneTree( params ):

    for id in params['nodeIds']:
        params['tree'].findNodeById(int(id)).prune()



def prune():
    

    nodeIds = request.vars.nodeIds.split(',')

    tree = getTree( { 'treeId': request.vars.treeId } )

    pruneTree( { 'tree': tree, 'nodeIds': nodeIds } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    rv = tree.emit_json()

    return rv
    

def updateTreeLabel( params ):

    nodeToUpdate = params['tree'].findNodeById( params['nodeId'] )

    if params['newLabel'] == '':
        nodeToUpdate.label = None
    else:
        nodeToUpdate.label = params['newLabel']


def editLabel():

    newick = local_import('newick')
    
    nodeId = int(request.vars.id)

    
    tree = getTree( { 'treeId': request.vars.treeId } )

    updateTreeLabel( { 'tree': tree, 'nodeId': nodeId, 'newLabel': request.vars.label } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )




def putChildrenBack( params ):

    if( hasattr( params['tree'], 'hiddenChildren' ) ):
        f.write( str(params['tree'].hiddenChildren ) )
        f.write( str(params['tree'].children ) )
        params['tree'].children = params['tree'].hiddenChildren

    for child in params['tree'].children:
        putChildrenBack( { 'tree': child } )



def hideChildren( params ):

    if( hasattr( params['tree'], 'hiddenChildren' ) ):
        params['tree'].children = []
    
    for child in params['tree'].children:
        hideChildren( { 'tree': child } )



def getNewick():

    import random, re
    newick = local_import('newick')

    randomInt = random.randrange(10000)
    
    tree = getTree( { 'treeId': request.vars.treeId } )

    putChildrenBack( { 'tree': tree } )

    f = open( ''.join( [ '/tmp/', str(randomInt), '.newick' ] ), 'w' )
    newickString = newick.to_string( tree )
    stripOut = re.compile( '()' )
    newickString = stripOut.sub( '', newickString )
    f.write( newickString )
    f.close()

    hideChildren( { 'tree': tree } )

    return randomInt



def save():
    import urllib,re,random
    svgFileHeader = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
    stripOut = re.compile( '<desc>.*</desc>')
    svgContent = stripOut.sub( '', urllib.unquote( request.vars.svg ) )
    randomInt = random.randrange(10000)
    f = open( ''.join( [ '/tmp/', str(randomInt), '.svg' ] ), 'w' )
    f.write( ''.join( [ svgFileHeader, svgContent ] ) )
    f.close()
    return randomInt


def receive():
    
    if request.args[0] == 'svg':
        response.headers['Content-Type'] = 'text/xml'
        response.headers["Content-Disposition"] = 'attachment;filename=svgfile.svg'
    else:
        response.headers['Content-Type'] = 'text/plain'
        response.headers["Content-Disposition"] = 'attachment;filename=newickfile.txt'

    f = open( ''.join( [ '/tmp/', request.args[1], '.', request.args[0] ] ), 'r')
    result = f.read()
    f.close()

    return result

def remove():
    import os
    os.remove( ''.join( [ '/tmp/', request.vars.id, '.', request.vars.type ] ) )


def delete():
    clearTreeCache( { 'treeId': request.vars.id } )
    del session.trees[ request.vars.id ]
    return 1
        

def removeCollapsed( params ):

    for child in params['node'].children:

        if( ( hasattr( child, 'hiddenChildren' ) ) & ( child.id not in params['collapsed'] ) ):
            child.isExpandable = False
            child.children = child.hiddenChildren
            del child.hiddenChildren

        elif( (child.id in params['collapsed']) & ( not hasattr( child, 'hiddenChildren' ) ) ):
            child.isExpandable = True
            child.hiddenChildren = child.children
            child.children = []
            
        removeCollapsed( { 'node' : child, 'collapsed' : params['collapsed'] } ) 



def findNode( params ):
   
    if( params['node'].id == params['nodeId'] ):
        return params['node']
    else:
        for child in params['node'].children:
            node = findNode( { 'node' : child, 'nodeId' : params['nodeId'] } )
            if node:
                return node



def updateScaling():

    session.trees[ request.vars.treeId ]['scaling'] = request.vars.scaling

    return request.vars.scaling


def expandClade():

    collapse = local_import('auto_collapse')

    cladeId = int(request.vars.cladeId)
    maxTips = int(request.vars.maxTips)
    
    tree = getTree( { 'treeId': request.vars.treeId } )

    collapsed = session.trees[ request.vars.treeId ]['collapseDict']
    visibleList = session.trees[ request.vars.treeId ]['visibleList']

    visibleList.append( cladeId )

    del collapsed[cladeId]

    expandingClade = findNode( { 'node': tree, 'nodeId': cladeId } )
    expandingClade.isExpandable = False
    expandingClade.children = expandingClade.hiddenChildren
    del expandingClade.hiddenChildren

    for child in expandingClade.children:
        if child.id in collapsed: del collapsed[child.id]
        visibleList.append( child.id )

    while (expandingClade.parent):
        if expandingClade.parent.id in collapsed: del collapsed[expandingClade.parent.id]
        visibleList.append( expandingClade.parent.id )
        expandingClade = expandingClade.parent

    visibleList = list ( set( visibleList ) )

    collapse.auto_collapse( tree, collapsed, visibleList, maxTips )

    removeCollapsed( { 'node' : tree, 'collapsed' : collapsed } )
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )
   
    return tree.emit_json()


def collapseClade():
    
    cladeId = int(request.vars.cladeId)

    visibleList = set(session.trees[ request.vars.treeId ]['visibleList'])
    collapsed = session.trees[ request.vars.treeId ]['collapseDict']
    collapsed[ cladeId ] = 1

    for id in visibleList:
        if id == cladeId:
            visibleList.remove(cladeId)
            break

    tree = getTree( { 'treeId': request.vars.treeId } )

    collapsingClade = tree.findNodeById( cladeId )
    collapsingClade.hiddenChildren = collapsingClade.children
    collapsingClade.children = []
    collapsingClade.isExpandable = True
    
    clearTreeCache( { 'treeId': request.vars.treeId } )
    
    storeTree( { 'treeId': request.vars.treeId, 'tree': tree } )

    return tree.emit_json()

    
def user():
    """
    exposes:
    http://..../[app]/default/user/login 
    http://..../[app]/default/user/logout
    http://..../[app]/default/user/register
    http://..../[app]/default/user/profile
    http://..../[app]/default/user/retrieve_password
    http://..../[app]/default/user/change_password
    use @auth.requires_login()
        @auth.requires_membership('group name')
        @auth.requires_permission('read','table name',record_id)
    to decorate functions that need access control
    """
    return dict(form=auth())

def download():
    """
    allows downloading of uploaded files
    http://..../[app]/default/download/[filename]
    """
    return response.download(request,db)


def call():
    """
    exposes services. for example:
    http://..../[app]/default/call/jsonrpc
    decorate with @services.jsonrpc the functions to expose
    supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
    """
    session.forget()
    return service()
